﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using YetAnotherChat.TcpServer.Interfaces;
using YetAnotherChat.TcpServer.WebSocketProtocols.rfc6455;

namespace YetAnotherChat.TcpServer
{
    /// <summary>
    /// A class represent a web socket client that connects to a web socket server at specified port, ip, path.
    /// Most functions are provided by ClientConnection class.
    /// This class handle open Tcp connection to server, then pass this Tcp connection into ClientConnection class.
    /// ClientConnection class handle async send and receive, also handle authentication with server.
    /// WebSocketClient hardcode the web socket protocol to connect to server (use Rfc6455).
    /// </summary>
    public class WebSocketClient : IDisposable, IWebSocketClient
    {
        public string Origin { get; set; }
        public string[] SubProtocols;
        public string CurrentProtocol { get; private set; }


        private TcpClient _client;
        
        private readonly string _path;
        private readonly int _port;
        private readonly string _host;

        public string Path
        {
            get
            {
                return _path;
            }
        }

        public int Port
        {
            get
            {
                return _port;
            }
        }

        public string Host
        {
            get
            {
                return _host;
            }
        }

        public event EventHandler<MessageEventArgs> MessageReceived;
        public event EventHandler<MessageEventArgs> MessgeSent;

        protected internal void OnMessageReceived(MessageEventArgs e)
        {
            if (MessageReceived != null)
                MessageReceived(this, e);
        }

        protected internal void OnMessgeSent(MessageEventArgs e)
        {
            if (MessgeSent != null)
                MessgeSent(this, e);
        }

        private static CancellationTokenSource cancellation = new CancellationTokenSource();

        public WebSocketClient(string path)
        {
            var r = new Regex("^(wss?)://(.*)\\:([0-9]*)/(.*)$");
            var matches = r.Match(path);

            _host = matches.Groups[2].Value;
            _port = Int32.Parse(matches.Groups[3].Value);
            _path = matches.Groups[4].Value;
        }

        /// <summary>
        /// Use a TcpClient to open a connection to server, then
        /// pass this client to a ClientConnection class to wrap around
        /// </summary>
        public void Connect()
        {
            if (_connection != null) return;

            try
            {
                _client = new TcpClient();
                _client.BeginConnect(_host, _port, OnRunClient, null);
            }
            catch (Exception)
            {
                Disconnect();
            }
        }

        private ClientConnection _connection;

        /// <summary>
        /// Fires when a successfully connects to a server.
        /// </summary>
        /// <param name="result">null</param>
        protected void OnRunClient(IAsyncResult result)
        {
            try
            {
                _client.EndConnect(result);
            }
            catch (Exception ex)
            {
                Disconnect();
            }

            //now creating a ClientConnection class, to handle async send and receive
            
            _connection = new ClientConnection(this, _client, new WebSocketProtocolFactory());

            _connection.BufferSize = 512;

            //hardcode the socket protocol to use Rfc6455
            _connection.Protocol = new WebSocketRfc6455Protocol(new Authentication()) { DataFrame = new WebSocketProtocols.rfc6455.DataFrame() };

            _connection.SetupSendReceiveProcess();
        }

        public void Disconnect()
        {
            if (_client == null) return;
            var dataFrame = new DataFrame();
            dataFrame.Append(new byte[0]);

            var bytes = dataFrame.AsFrame()[0].Array;

            bytes[0] = 0x88;
            _connection.Send(bytes);
            _connection.Disconnect();
        }

        public void Send(String data)
        {
            _connection.Send(data);
        }

        public void Send(byte[] data)
        {
            _connection.Send(data);
        }

        public void Send(object obj)
        {
            _connection.Send(obj);
        }

        public void Dispose()
        {
            cancellation.Cancel();
        }

    }
}
